/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package de.saar.penguin.give3dreplay.eyetracking;

import de.saar.penguin.give3dreplay.ReplayWindow3D;
import give.comm.ActionMessage;
import give.comm.ClientMetadata;
import give.comm.EventSourceMessage;
import give.comm.Message;
import give.comm.StatusMessage;
import give.comm.handling.MessageHandlingException;
import give.formula.Formula;
import give.comm.handling.EventSource;
import give.replay.ReplayWindow;
import give.world.World;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 *
 * @author koller
 */
public class InspectionEventSource extends EventSource implements ReplayWindow {
    public static final String INSPECTIONTIME = "inspection-time";
    public static final String ISNEW = "is-new";
    public static final String OBJECT = "object";
    //

    private static enum State {
        NOT_INSPECTING, WAITING_FOR_INSPECTION_MINTIME, INSPECTING, INSPECTION_PAUSED
    };
    private State state = State.NOT_INSPECTING;
    private static ReplayWindow3D replayWindow = null;
    private String eyetrackingEventSource;
    private String previousObject = null;
    private long inspectionStartTime = 0;
    private long inspectionPauseStartTime = 0;
//    private boolean isNewInspection = false;
    private long minimumFixationTime;
    private long maximumPauseTime;
    private boolean windowOpen; // ignore all messages, except for action messages, until a window has first been opened

    public InspectionEventSource(String name, Map<String, String> parameters) {
        super(name);

        windowOpen = false;
        eyetrackingEventSource = parameters.get("eyetracking-source");
        minimumFixationTime = Long.parseLong(parameters.get("minimum-time"));
        maximumPauseTime = Long.parseLong(parameters.get("maximum-pause-time"));

        if (replayWindow == null) {
            final int windowWidth = Integer.parseInt(parameters.get("window-width"));
            final int windowHeight = Integer.parseInt(parameters.get("window-height"));

            replayWindow = new ReplayWindow3D(windowWidth, windowHeight);
            replayWindow.setEyetrackingEventSourceName(eyetrackingEventSource);
        }
    }

    public void synchronizeWorld() {
        replayWindow.synchronizeWorld();
    }

    public void openReplayWindow(World world, String windowTitle, ClientMetadata clientMetadata) {
        replayWindow.openReplayWindow(world, windowTitle, clientMetadata);
        windowOpen = true;
    }

    public void handleStatusMessage(StatusMessage sm) throws MessageHandlingException {
        if (windowOpen) {
            replayWindow.handleStatusMessage(sm);
        }
    }

    public void handleMessage(Message msg) throws MessageHandlingException {
        if (windowOpen) {
            replayWindow.handleMessage(msg);

            // if replay window is drawing itself properly, interpret eyetracking messages into inspection messages
            if (replayWindow.isReady()) {
                if (msg instanceof EventSourceMessage) {
                    EventSourceMessage esm = (EventSourceMessage) msg;

                    if (esm.getEventSource().equals(eyetrackingEventSource)) {
                        long timestamp = getTimestamp(esm);
                        List<String> fixatedObjects = replayWindow.getFixatedObjects(getX(esm), getY(esm));

                        switch (state) {
                            case NOT_INSPECTING:
                                if (!fixatedObjects.isEmpty()) {
                                    previousObject = fixatedObjects.get(0);
                                    inspectionStartTime = timestamp;
                                    inspectionPauseStartTime = 0;
                                    state = State.WAITING_FOR_INSPECTION_MINTIME;
                                }
                                break;


                            case WAITING_FOR_INSPECTION_MINTIME:
                                if (fixatedObjects.isEmpty()) {
                                    state = State.NOT_INSPECTING;
                                } else {
                                    if (timestamp - inspectionStartTime >= minimumFixationTime) {
                                        previousObject = fixatedObjects.get(0);
                                        notifyListeners(makeMap(timestamp, true));
                                        state = State.INSPECTING;
                                    }
                                }
                                break;

                            case INSPECTING:
                                if (fixatedObjects.isEmpty()) {
                                    inspectionPauseStartTime = timestamp;
                                    state = State.INSPECTION_PAUSED;
                                } else if (fixatedObjects.get(0).equals(previousObject)) {
                                    notifyListeners(makeMap(timestamp, false));
                                    // stay in INSPECTING
                                } else {
                                    previousObject = fixatedObjects.get(0);
                                    inspectionStartTime = timestamp;
                                    inspectionPauseStartTime = 0;
                                    state = State.WAITING_FOR_INSPECTION_MINTIME;
                                }
                                break;

                            case INSPECTION_PAUSED:
                                if (fixatedObjects.isEmpty()) {
                                    if (timestamp - inspectionPauseStartTime > maximumPauseTime) {
                                        previousObject = null;
                                        state = State.NOT_INSPECTING;
                                    }
                                } else if (fixatedObjects.get(0).equals(previousObject)) {
                                    state = State.INSPECTING;
                                } else {
                                    previousObject = fixatedObjects.get(0);
                                    inspectionStartTime = timestamp;
                                    inspectionPauseStartTime = 0;
                                    state = State.WAITING_FOR_INSPECTION_MINTIME;
                                }
                        }
                    }
                }
            }
        }
    }

    private static long getTimestamp(EventSourceMessage esm) {
        return 1000 * Long.parseLong(esm.getData().get("frameTimeSeconds")) + Long.parseLong(esm.getData().get("frameTimeMilliseconds"));
    }

    private static int getX(EventSourceMessage esm) {
        return Integer.parseInt(esm.getData().get("x"));
    }

    private static int getY(EventSourceMessage esm) {
        return Integer.parseInt(esm.getData().get("y"));
    }

    public void handleException(MessageHandlingException mhe) {
        replayWindow.handleException(mhe);
    }

    public void handleActionMessage(ActionMessage am, List<Formula> list) throws MessageHandlingException {
        // unlike other messages, action messages should be enqueued in the replay window even if there
        // is no open window, in order to ensure that the replay window world starts in the right state
        replayWindow.handleActionMessage(am, list);
    }

    @Override
    public void start() {
    }

    @Override
    public void shutdown() {
    }

    private Map<String, String> makeMap(long timestamp, boolean isNew) {
        Map<String, String> ret = new HashMap<String, String>();

        ret.put(OBJECT, previousObject);
        ret.put(INSPECTIONTIME, Long.toString(timestamp - inspectionStartTime));
        ret.put(ISNEW, Boolean.toString(isNew));

        return ret;
    }
}
